/*
  The JTAG Whisperer: An Arduino library for JTAG.

  By Mike Tsao <http://github.com/sowbug>.

  Copyright © 2012 Mike Tsao. Use, modification, and distribution are
  subject to the BSD-style license as described in the accompanying
  LICENSE file.

  See README for complete attributions.
*/

#include <BitTwiddler.h>
#include <JTAGWhisperer.h>
#include <SerialComm.h>

const int BLINK_PIN = 13;
static bool is_pin_on;
void blink() {
  digitalWrite(BLINK_PIN, is_pin_on);
  is_pin_on = !is_pin_on;
}

void blinker()
{
  for (int i = 0; i < 10; ++i) {
    blink();
    delay(50);
  }
}

void setup() {
  pinMode(BLINK_PIN, OUTPUT);
  blinker();
}

// This is out of loop() so we get RAII without looping.
void do_it() {
  SerialComm serial_comm;
  BitTwiddler twiddler;
  JTAGWhisperer whisperer(serial_comm, twiddler);
  uint32_t instruction_count = 0;

  while (true) {
    blink();
    uint8_t instruction = whisperer.read_next_instruction();
    if (!whisperer.handle_instruction(instruction)) {
      if (!whisperer.reached_xcomplete()) {
        serial_comm.Important("Failure at instruction #%d", instruction_count);
      }
      break;
    }
    ++instruction_count;
  }
  serial_comm.Important("Processed %d instructions.", instruction_count);
}

void setPwmFrequency(int pin, int divisor)
{
  byte mode;
  switch(divisor)
  {
    case 1: mode = 0x01; break;
    case 8: mode = 0x02; break;
    case 32: mode = 0x03; break;
    case 64: mode = 0x04; break;
    case 128: mode = 0x05; break;
    case 256: mode = 0x06; break;
    case 1024: mode = 0x7; break;
    default: return;
  }
  TCCR2B = TCCR2B & 0b11111000 | mode;
}

void fastClock(void)
{
  TCCR2A = 0xB3 ; // fast PWM with programmed TOP val
  TCCR2B = 0x09 ; // divide by 1 prescale
  TCNT2  = 0x00 ;
  OCR2A  = 0x0F ; // TOP = 15, cycles every 16 clocks
  OCR2B  = 0x07 ; // COMP for pin3
}

void adbWrite(unsigned char n)
{
  DDRC = 0b00011111;
  PORTC = (n & 0x0f) + 16;        // bit 4 indicates LSB on ADB
  PORTD &= ~0x40;                 // write strobe
  PORTD |= 0x40;                  //
  PORTC = n >> 4;                 // write MSB
  PORTD &= ~0x40;                 // write strobe
  PORTD |= 0x40;                  //
}

unsigned char adbRead()
{
  DDRC = 0b00010000;

  PORTC |= 0x10;                    // go for the lsb
  PORTD &= ~0x80;                 // rd strobe low
  delayMicroseconds(1);           // let the data lines settle
  unsigned char m = PINC & 0x0f;  // grab the lsb
  PORTD |= 0x80;                  // rd strobe high

  PORTC &= ~0x10;                   // msb
  PORTD &= ~0x80;                 // rd strobe low
  delayMicroseconds(1);           // let the data lines settle
  m |= (PINC  << 4);              // grab the msb
  PORTD |= 0x80;                  // rd strobe high

  return m;
}

// Wait until the z80 has finished a read cycle - high, low, high
// Used when feeding data to the databus.
#define WAITREAD  while((PIND &0x04) == 0x04){}; while((PIND &0x04) == 0x00){};

// Watch for an IO request - signalled by the CPLD forcing the WAIT line true
// Check NRDWR to see if it was a read or a write
#define WAITIO  while((PINC &0x20) == 0x20){};
#define WAITNOIO  while((PINC &0x20) == 0x00){};

#define RESUME PORTD &= ~0x20; PORTD |= 0x20;


unsigned char rawData[70] = {
	0x21, 0x11, 0x00, 0x06, 0x35, 0x7E, 0xD3, 0x80, 0x23, 0x10, 0xFA, 0xDB,
	0x80, 0xD3, 0x80, 0x18, 0xFA, 0x0A, 0x0D, 0x48, 0x65, 0x6C, 0x6C, 0x6F,
	0x2C, 0x20, 0x77, 0x6F, 0x72, 0x6C, 0x64, 0x21, 0x0A, 0x0D, 0x41, 0x72,
	0x64, 0x75, 0x69, 0x6E, 0x6F, 0x20, 0x6D, 0x65, 0x65, 0x74, 0x73, 0x20,
	0x5A, 0x38, 0x30, 0x20, 0x69, 0x6E, 0x20, 0x74, 0x68, 0x65, 0x20, 0x41,
	0x52, 0x38, 0x30, 0x20, 0x3A, 0x44, 0x0A, 0x0D, 0x0A, 0x0D
};


/*

 PORTB
 4          : D12  : IN/OUT - Z80 !RESET     | normally input, only briefly out to reset the z80

 PORTC
 0, 1, 2, 3 : A0-3 : IN/OUT - ADBDATA[0:3]   |
 4          : A4   : OUT    - LSB            | ADB LSB access flag. 
 5          : A5   : IN     - !WAIT          | z80 is paused awaiting fulfilment of an IO request

 PORTD
 2          : D2   : IN     - !RD/WR         | 0 when z80 is reading, 1 else
 3          : D3   : OUT    - CLK            | PWM -> z80
 4          : D4   : OUT    - !BL            | bootloading is in progress
 5          : D5   : OUT    - !WAITCLR       | release the wait line after an IO request
 6          : D6   : OUT    - !ADBWR         | write 4 bits from ADBDATA lines to the latch
 7          : D6   : OUT    - !ADBRD         | read 4 bits of the latch from the ADBDATA lines

*/

void loop() {
  Serial.begin(115200);
  
  // d12 is an input usually, with a pull-up
  DDRB &= ~0x10;
  PORTB |= 0x10;
  
  if ((PINB & 0x10) == 0)
  {
    // reset button on-board is held, go into jtag whisperer
    do_it();
  }
  else
  {
    Serial.print("Z80 Bootload begin .");
  }

  // set all the analog inputs to be digital outputs,
  // disabling the pull-ups while we're at it
  for (int i = 0; i < 6; ++i)
  {
    digitalWrite(A0+i,LOW);
  }

  // 50% duty, minimum divider = 32khz for the clock
  pinMode(3, OUTPUT);
  analogWrite(3, 127);
  setPwmFrequency(3, 1);

  // PINB-2    = nRESET   (I/O)
  DDRB &= ~0x04;

  // PINC-0..3 = ADP      (I/O)
  // PINC-4    = LSB      (O)
  // PINC-5    = nWAIT    (I)
  DDRC  = 0b00010000;

  // PINC-2    = nRDWR    (I)
  // PINC-3    = CLK      (O)
  // PINC-4    = nBL      (O)
  // PINC-5    = nWAITCLR (O)
  // PINC-6    = mADPWR   (O)
  // PINC-7    = nADPRD   (O)
  DDRD  = 0b11111011;
  PORTD = 0b11110000;

  unsigned char m, n;

#if(0)
  for(;;)
  {
    ++n;
    adbWrite(n);
    m = adbRead();
    if (m!=n)
    {
      Serial.print(n, DEC);
      Serial.print(" ");
      Serial.println(m, DEC);
    }
    if(0==n)
    {
      Serial.print(".");
    }
  }
#endif

// if we want to drop into the ICE
#if(0)
  DDRB |= 0b0001000;    // set reset line as an output
  PORTB &= ~0x10;      // assert reset
  fastClock();        // 1mhz woooh!
  delay(1);          // settling time
  PORTB |= 0x10;      // release reset
  DDRB &= 0b11101111;  // input again
  while(1){};
#endif



  DDRB |= 0x10;            // set reset line as an output
  PORTB &= ~0x10;          // hold z80 in reset

  PORTD &= ~0x10;          // set BL mode
  setPwmFrequency(3, 8);   // set a nice slow clock for this bit

// fix until reset is factored in to signals
  adbWrite(0);
  RESUME;

  PORTB |= 0x10;           // allow z80 to start
  DDRB &= ~0x10;           // input again

  WAITREAD;                // wait until the latched byte is read
  WAITREAD;                // wait until the latched byte is read
  WAITREAD;                // wait until the latched byte is read
  WAITREAD;                // wait until the latched byte is read
  WAITREAD;                // wa++++++++++++++it until the latched byte is read
  WAITREAD;                // wait until the latched byte is read
  adbWrite(0x21);          // pre-warm the latch with the opcode for LD HL,nnnn
  WAITREAD;                // wait until the latched byte is read
  adbWrite(0);             // write the argument - 0x00, 0x00
  WAITREAD;                // it needs to be read twice
                           // - but defer the second read so we can sink the for-loop setup cost

  for (int i = 0; i < sizeof(rawData); ++i)
  {
    WAITREAD;

    adbWrite(0x3e);        // LD A,nn
    WAITREAD;
    adbWrite(rawData[i]);  // (nn)
    WAITREAD;
    adbWrite(0x77);        // LD (HL),a
    WAITREAD;
    adbWrite(0x23);        // INC HL
    if (i & 1) Serial.write('.');
  }

  // wait until the INC HL opcode has been executed then send a NOP.
  // once this starts to execute we can safely reset the z80
  WAITREAD;
  adbWrite(0);
  WAITREAD;
  WAITREAD;

  Serial.println("\r\nBootload complete. Entering IO handler.");
 
  // the whole program has been written and the z80 is now executing NOPs
  // reset the z80 again 
  DDRB |= 0x10;
  PORTB &= ~0x10;

  PORTD |= 0x10;      // clear bootload flag
  setPwmFrequency(3, 8);
  delay(1);

  PORTB |= 0x10;
  DDRB &= ~0x10;       // input again

  n = 'a';
  while (true)
  {
    WAITIO;
    if ((PIND & 0x04) == 0)
    {
      // it was an IO read - supply a byte
      adbWrite(n);
      ++n;
      if (n == 'z' + 1)
      {
        n = 'a';
      }
      if (n == 'b')
      {
        Serial.println(" :)");
      }
    }
    else
    {
      // it was an IO write - grab a byte to eat
      m = adbRead();
      Serial.write(m);
    }
    RESUME;
    WAITNOIO;
  }
}

